#include <atomic>
#include <cstdio>
#include <iomanip>
#include <cerrno>
#include <iostream>

#include "simple.hpp"
#include "plain_button.h"
#include "layout.h"
#include "digits.h"
#include "ecs.hpp"
#include "utils.hpp"
#include "digit_display.h"
#include "timer.h"

// TODO: get rid of these, are here to generate the focus_group and layout of entity iterator range, define specific entity types and explicitly instantiaste them in a separate cpp file
#include "implementation.hpp"
#include "layout.hpp"

// const std::chrono::steady_clock::duration max_duration = 99h + 59min + 59s;
// const std::chrono::steady_clock::duration min_duration{};

struct nothing {};
using movement = motion::movement<std::chrono::steady_clock::duration, nothing, nothing>;

int main(int argc, const char** argv) try
{
	using support::ston;

	const auto main_color = argc > 2
		? rgb_pixel::from(ston<rgb_pixel::int_type>(argv[2]))
		: 0x009dff_rgb; // or 0x3ba3cd_rgb;

	const auto second_color = argc > 3
		? rgb_pixel::from(ston<rgb_pixel::int_type>(argv[3]))
		: 0x000000_rgb;

	const std::chrono::milliseconds frametime (argc > 4 ? ston<unsigned>(argv[4]) : 33);

	if(main_color == second_color) // would be cool to have a smarter contrast check
	{
		std::fputs("foreground and background colors should differ", stderr);
		std::fputs("\n", stderr);
		return -1;
	}

	std::string icon_string =
		"---------"  // "---------"
		"+++++++++"  // "-+++++++-"
		"--+------"  // "----+----"
		"--+-+---+"  // "----+----"
		"--+-+-+-+"  // "----+----"
		"--+-+-+-+"  // "----+----"
		"--+-+-+-+"  // "----+----"
		"--+-+++++"  // "----+----"
		"---------"  // "---------"
	;

	initializer init;

	surface icon_small(reinterpret_cast<surface::byte*>(icon_string.data()), {9,9},
		pixel_format(pixel_format::type::index8));
	icon_small.format().palette()->set_color('+', main_color);
	icon_small.format().palette()->set_color('-', 0x0_rgba);

	graphical::software_window win("truwo", {400,400}, graphical::window::flags::resizable);
	auto fg_color = win.surface().format().color(main_color);
	auto bg_color = win.surface().format().color(second_color);

	surface icon({64,64}, pixel_format(pixel_format::type::rgba8888));
	blit(convert( icon_small, icon.format()), icon, rect{icon.size()});
	win.icon(icon);

	auto music = argc > 1
		?  argv[1][0] != '\0' ? std::optional<musical::wav>(argv[1]) : std::nullopt
		: std::optional<musical::wav>("./truwo.wav");

	std::atomic<bool> music_playing = false;
	// std::atomic<bool> music_done = false;
	auto player = [&music_playing, &music, i = music->buffer().begin()](auto& device, auto buffer) mutable
	{
		if(!music_playing)
		{
			std::fill(buffer.begin(), buffer.end(), device.silence());
			return;
		}

		const auto buffer_size = buffer.end() - buffer.begin();
		const auto size = std::min<size_t>(buffer_size, music->buffer().end() - i);
		std::copy_n(i, size, buffer.begin());

		i += size;
		if(i == music->buffer().end())
		{
			i = music->buffer().begin();
			music_playing = false;
		}
	};
	using music_device = musical::device_with_callback<decltype(player)>;
	std::unique_ptr<music_device> device = nullptr;

	bool paused = true;

	components<
		object_interface<
			i_ui_object,
			i_graphic,
			i_interactive,
			i_movable_bounds<int2>
		>,
		movement,
		focus_vector,
		bounds_layout_vector
	> components {};

	std::cerr << "----------------INIT----------------" << '\n';
	std::cerr << components.log_sizes() << '\n';

	rect button_rect{win.size()/8 + int2{5,5}};
	auto make_control_button = [&]() -> auto&
	{
		auto& button = components.emplace<plain_button>(fg_color, button_rect);
		return button;
	};
	auto& stop_button = make_control_button();
	auto& down_button = make_control_button();

	std::cerr << "----------------BUTTONS----------------" << '\n';
	std::cerr << components.log_sizes() << '\n';

	focus_vector button_focus_group{
		{stop_button, down_button} };

	focus_vector main_focus_group;

	auto focus_handler = [&main_focus_group](auto& element)
	{
		main_focus_group.focus_on(element);
	};

	stop_button.on_press.push_back(focus_handler);
	down_button.on_press.push_back(focus_handler);

	// oof
	const auto & const_components = components;
	auto& movable_bounds = const_components.get<i_movable_bounds<int2>*>();

	auto button_range_begin = std::find(movable_bounds.begin(), movable_bounds.end(),
		(i_movable_bounds<int2>*)(&stop_button));
	auto button_range = support::offset_range
	{
		movable_bounds,
		support::range
		{
			button_range_begin,
			std::next(std::find(movable_bounds.begin(), movable_bounds.end(),
				(i_movable_bounds<int2>*)(&down_button)))
		} - button_range_begin
	};

	bounds_layout button_layout (button_range, int2::i(5));
	button_layout.update();

	entities entities {std::move(components)};

	std::cerr << "----------------ENTITIES----------------" << '\n';
	std::cerr << entities.log_sizes() << '\n';

	auto make_timer_ui = [&entities, &fg_color, &focus_handler](auto last_total_duration)
	{
		return entities.make([&](auto& components)
		{
			int2 digit_size{40,100};
			auto digit_spacing = int2::i(5);
			auto make_time_display = [&]() -> auto&
			{
				// oof, template -_-
				auto& display = components.template emplace<digit_display<>>(digit_size, digit_spacing, fg_color);
				return display;
			};

			auto& hours_display = make_time_display();
			auto& minutes_display = make_time_display();
			auto& seconds_display = make_time_display();

			rect separator_rect{{13,100}};
			components.template emplace<bounds_layout_vector>(
				std::vector<i_movable_bounds<int2>*>{
					&hours_display,
					&components.template emplace<digit_bitmap>(digit[10], fg_color, separator_rect),
					&minutes_display,
					&components.template emplace<digit_bitmap>(digit[10], fg_color, separator_rect),
					&seconds_display
				},
				int2::i(5)
			).update();

			components.template push( focus_vector{{hours_display, minutes_display, seconds_display}} );

			hours_display.on_press.push_back(focus_handler);
			minutes_display.on_press.push_back(focus_handler);
			seconds_display.on_press.push_back(focus_handler);

			components.template emplace<movement>(movement{last_total_duration});

			std::cerr << "----------------INMAKE----------------" << '\n';
			std::cerr << components.log_sizes() << '\n';

			return std::tie(
				hours_display,
				minutes_display,
				seconds_display
			);

		});
	};

	std::vector<decltype(make_timer_ui(0ms))> timer_displays;

	auto timer_layout = bounds_layout{ simple::support::range{
		entities.get_component_iterator<bounds_layout_vector>(timer_displays.begin()),
		entities.get_component_iterator<bounds_layout_vector>(timer_displays.end()),
	}, int2::j(15)};

	bounds_layout_vector main_layout({&button_layout, &timer_layout}, int2::j(15));
	main_layout.update();

	main_layout += int2(10,10);

	auto current_timer = motion::symphony{ simple::support::range{
		entities.get_component_iterator<movement>(timer_displays.begin()),
		entities.get_component_iterator<movement>(timer_displays.end()),
	}};

	auto timer_focus_group = focus_group{ simple::support::range{
		entities.get_component_iterator<focus_vector>(timer_displays.begin()),
		entities.get_component_iterator<focus_vector>(timer_displays.end()),
	}};

	main_focus_group = focus_vector{{ button_focus_group, timer_focus_group }};

	auto reset_current_timer = [&]()
	{
		music_playing = false;
		init.graphics.screensaver.release_one();
		device = nullptr;
		current_timer.reset();
		paused = true;
	};

	stop_button.on_click.push_back([&](auto&)
	{
		if(music_playing && current_timer.done())
		{
			reset_current_timer();
		}
		else
		{
			paused = true;
		}
	});

	down_button.on_click.push_back([&](auto&)
	{
		paused = false;
	});

	timer stop_button_hold(1s);
	stop_button.on_press.push_back([&stop_button_hold](auto&)
	{
		stop_button_hold = timer(stop_button_hold.duration(), true);
	});

	stop_button.on_release.push_back([&stop_button_hold](auto&)
	{
		stop_button_hold.pause();
	});

	bool done = false;
	std::chrono::steady_clock::time_point current_time;
	while(!done)
	{
		const auto new_time = std::chrono::steady_clock::now();
		const auto time_delta = new_time - current_time;
		current_time = new_time;

		using namespace interactive;
		while(auto event = next_event())
		{
			std::visit(support::overload{
				[&done](quit_request) { done = true; },
				[&win](window_size_changed)
				{
					win.update_surface();
				},
				[&main_focus_group](const mouse_down&)
				{
					main_focus_group.drop_focus();
				},
				[&entities, &main_focus_group, &timer_focus_group, &timer_layout, &timer_displays, &make_timer_ui, &main_layout, &current_timer](const key_pressed& e)
				{
					if(e.data.keycode == keycode::tab)
					{
						auto direction =
							pressed(scancode::rshift) ||
							pressed(scancode::lshift)
								? i_focusable::prev
								: i_focusable::next
						;

						if(!main_focus_group.focus(direction))
						{
							main_focus_group.drop_focus();
							main_focus_group.focus(direction);
						}

					}
					else if(e.data.keycode == keycode::n)
					{
						auto last_total_duration = empty(timer_displays) ? 0ms
							: entities.find<movement>(timer_displays.back().id).begin()->total;
						auto& entity = timer_displays.emplace_back(make_timer_ui(last_total_duration));

						std::cerr << "----------------NEWTIMER----------------" << '\n';
						std::cerr << "timer displays: " << timer_displays.size() << '\n';
						std::cerr << entities.log_sizes() << '\n';


						auto [h,m,s] = entity.components;

						current_timer = motion::symphony{ simple::support::range{
							entities.get_component_iterator<movement>(timer_displays.begin()),
							entities.get_component_iterator<movement>(timer_displays.end()),
						}};

						main_focus_group.drop_focus();
						timer_focus_group = focus_group{ simple::support::range{
							entities.get_component_iterator<focus_vector>(timer_displays.begin()),
							entities.get_component_iterator<focus_vector>(timer_displays.end()),
						}};

						timer_layout = bounds_layout{ simple::support::range{
							entities.get_component_iterator<bounds_layout_vector>(timer_displays.begin()),
							entities.get_component_iterator<bounds_layout_vector>(timer_displays.end()),
						}, int2::j(15)};
						timer_layout.update();
						main_layout.update();

						h.on_input.push_back([&entities, entity_id = entity.id](auto&&, int old_value, int new_value)
						{
							using namespace std::chrono;
							auto timer = entities.find<movement>(entity_id).begin();
							auto offset = hours(new_value) - hours(old_value);
							timer->reset();
							timer->total = timer->total - timer->elapsed + offset;
						});
						m.on_input.push_back([&entities, entity_id = entity.id](auto&&, int old_value, int new_value)
						{
							using namespace std::chrono;
							auto timer = entities.find<movement>(entity_id).begin();
							auto new_minutes = minutes(new_value);
							if(new_minutes >= hours(1))
								new_minutes = hours(1) - minutes(1);
							auto offset = new_minutes - minutes(old_value);
							timer->reset();
							timer->total = timer->total - timer->elapsed + offset;
						});
						s.on_input.push_back([&entities, entity_id = entity.id](auto&&, int old_value, int new_value)
						{
							using namespace std::chrono;
							auto timer = entities.find<movement>(entity_id).begin();
							auto new_seconds = seconds(new_value);
							if(new_seconds >= minutes(1))
								new_seconds = minutes(1) - seconds(1);
							auto offset = new_seconds - seconds(old_value);
							timer->reset();
							timer->total = timer->total - timer->elapsed + offset;
						});

					}
				},
				[](auto) { }
			}, *event);

			for(auto&& interactive : entities.get_all<i_interactive*>())
				interactive->update(*event);
		}

		// TODO:
		// use proper iterator that return ranges, and steps based on entity_size instead of the base entity iterator
		for
		(
			auto [hours, minutes, seconds, timer, end] = std::tuple
			{
				entities.get_component_iterator<i_graphic*>(timer_displays.begin()),
				entities.get_component_iterator<i_graphic*, 1>(timer_displays.begin()),
				entities.get_component_iterator<i_graphic*, 2>(timer_displays.begin()),
				entities.get_component_iterator<movement>(timer_displays.begin()),
				entities.get_component_iterator<movement>(timer_displays.end())
			};
			timer != end;
			++hours, ++minutes, ++seconds, ++timer
		)
		{
			auto duration = timer->total - timer->elapsed;
			static_cast<digit_display<>*>(*hours)->set(extract_duration<std::chrono::hours>(duration).count());
			static_cast<digit_display<>*>(*minutes)->set(extract_duration<std::chrono::minutes>(duration).count());
			static_cast<digit_display<>*>(*seconds)->set(extract_duration<std::chrono::seconds>(duration).count());
		}

		fill(win.surface(), bg_color);

		for(auto&& graphic : entities.get_all<i_graphic*>())
			graphic->draw(win.surface());
		win.update();

		down_button.enable(!music_playing && paused);
		// TODO:
		// use proper iterator that return ranges, and steps based on entity_size instead of the base entity iterator
		for
		(
			auto [hours, minutes, seconds, end] = std::tuple
			{
				entities.get_component_iterator<i_interactive*>(timer_displays.begin()),
				entities.get_component_iterator<i_interactive*>(timer_displays.begin()),
				entities.get_component_iterator<i_interactive*>(timer_displays.begin()),
				entities.get_component_iterator<i_interactive*>(timer_displays.end()),
			};
			hours != end;
			++hours, ++minutes, ++seconds
		)
		{
			static_cast<ui_element*>(*hours)->enable(!music_playing && paused);
			static_cast<ui_element*>(*minutes)->enable(!music_playing && paused);
			static_cast<ui_element*>(*seconds)->enable(!music_playing && paused);
		}

		if(stop_button_hold.check())
			reset_current_timer();

		if(!music_playing)
		{
			if(timer_displays.size() != 0 && current_timer.done())
			{
				reset_current_timer();
			}
		}

		if(!paused)
		{
			auto result = current_timer.advance(time_delta);

			for(auto&& timer : result.updated)
			{
				if(timer.done())
				{
					if(!music_playing)
					{
						music_playing = true;
						main_focus_group.focus_on(stop_button);
						if(music)
						{
							device = std::make_unique<music_device>(
								musical::basic_device_parameters{music->obtained()},
								player
							);
							device->play();
						}
					}
				}
			}
			if(result.done)
			{
				paused = true;
				// TODO:
			}
		}

		const auto next_frame_time = current_time + frametime;
		// const auto next_frame_time = paused
		// 	? current_time + frametime
		// 	: min(current_timer.target_time_point(), current_time + frametime);
		std::this_thread::sleep_until(next_frame_time);

	}

	return 0;
}
catch(...)
{
	if(errno)
		std::perror("ERROR");

	const char* sdl_error = SDL_GetError();
	if(*sdl_error)
	{
		std::fputs(sdl_error, stderr);
		std::fputs("\n", stderr);
	}

	throw;
}
